Part II
Documents, Views, and Applications That Use Them

In This Part

  The Document/View Architecture 275
  Extending the User Interface 301
  Printing 345

Chapter 7
The Document/View Architecture

by K. David White

In This Chapter

  Documents, Frames, and Views 276
  Creating New Documents 280
  Views 285

In most books on MFC and Visual C++, the document/view architecture is usually given top billing. There are reasons for this. Microsoft, in developing MFC, decided that a consistent framework was needed to encapsulate the Windows functionality. Although some might argue that the document/view architecture is really all you need to understand in order to create robust MFC applications, it is apparent to the experienced MFC developer that this is not the case. The document/view architecture is a consistent framework and provides the necessary control over MFC applications; however, it lends itself to expansion and flexibility. There are also many situations in which the doc/view architecture should be abandoned entirely. This chapter tries to unlock the doc/view framework and concentrate on using it to the best advantage.

The first section covers all the components and provides an overview of the doc/view architecture. The “Creating New Documents” section explores the means necessary to utilize the framework to create new documents. Next, the chapter explores views and how the views interact with the document and frames. After you gain an understanding of views, you’ll take a look at the base of this framework, the document. With this knowledge of all the pieces, you’ll look to ways to manage it above and beyond the framework. The last section of this chapter takes a quick look at other frameworks to consider.

Documents, Frames, and Views

Most senior-level developers understand how data should be managed and how it should be presented. In object-oriented presentation, a class contains member variables that are accessed through member functions. This encapsulation ensures that the class is basically responsible for itself. How then does an application that contains many classes manage the data contained within those classes in a controlled and structured manner? If you look at the problem of presenting data versus managing data, you can quickly see where the document/view architecture solves this dilemma. Let’s take a closer look.

As you can easily see from Figure 7.1, the document contains the data store, and the view is a window into a certain location of that data. This store can be ultimately a database connection, a disk storage file, or some other mechanism. Later sections look at the problem of where to store data needed by a view. Simply put, however, the view is a window into the data that is stored within the document. Within this architecture, the document is responsible for exposing the necessary interfaces for the view to display the data.


Figure 7.1  A simple representation of the document/view architecture.

The doc/view architecture comes in two primary flavors: the Single Document Interface (SDI) and the Multiple Document Interface (MDI). An SDI application contains one and only one document class with which to control the data. The MDI application can contain any number of documents. You will notice from Figure 7.2 that the documents are created through a document template class.


Figure 7.2  Document types within the doc/view architecture.

The solid dark lines in Figure 7.2 represent the instantiation path for creating views, and the dotted lines represent the pointer direction. Now that you’ve seen how this picture is painted, let’s start looking at each individual piece of the puzzle.


Note:  

There are many different document classes, which are usually derived from the base class CDocument. For instance, if you are using COM and want compound document support, you might want to choose the COleDocument class.



Note:  

As Chapter 27, “MAPI and MFC,” points out, the CDocument class also provides the necessary structure to support the Microsoft Messaging Application Program Interface (MAPI). This is done through the two CDocument member functions: OnFileSendMail and OnUpdateFileSendMail


Document Templates

Imagine, if you will, trying to manage all the necessary information about data without the different classes to define the structure of the data. But what about relating the data contained within the document with the necessary display constraints defined by a view? Some overhead mechanism is needed to keep track of the document and its relation to the views that it owns. Enter the document template.

The document template class defines all the menus, accelerators, and other important resources that a view requires. This template mechanism loosely ties the document class with the view class.

As Figure 7.2 shows, the CWinApp class creates and contains a pointer to one or more document templates, depending on whether the application is an SDI or MDI application. CSingleDocTemplate is responsible for managing one and only one CDocument class. CMultiDocTemplate maintains a list of CDocument classes that are currently opened from that template. MFC breaks the object-oriented paradigm (only a little bit) here by allowing the CDocument class and the CDocTemplate classes to be friends. The CDocument contains a back pointer to its owning CDocTemplate class. This allows an application to traverse the CWinApp/CDocument/CView hierarchy in either direction.

Whenever the CWinApp class creates a document template, it does so in a two-step process. It first instantiates the DocTemplate class and then performs an AddDocument to add the document to the template’s list and sets the back pointer. Listing 7.1 shows the CSingleDocTemplate constructor, and Listing 7.2 shows the AddDocument method.

Notice that the back pointer is set during the AddDocument method. One reason that it is done this way is to enable the user to create templates and specify document types outside what the framework automatically provides. Listing 7.3 is from the SDI sample UNL_Single.

Listing 7.1 The CSingleDocTemplate Constructor


CSingleDocTemplate::CSingleDocTemplate(UINT nIDResource,
  CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass,
  CRuntimeClass* pViewClass)
  : CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)
{
  m_pOnlyDoc = NULL;

}

Listing 7.2 The CSingleDocTemplate AddDocument Method


void CSingleDocTemplate::AddDocument(CDocument* pDoc)
{
    ASSERT(m_pOnlyDoc == NULL);   // one at a time, please
    ASSERT_VALID(pDoc);

    CDocTemplate::AddDocument(pDoc);
    m_pOnlyDoc = pDoc;

}

Listing 7.3 The UNL_Single.cpp InitInstance Routine


CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
    IDR_MAINFRAME,
    RUNTIME_CLASS(CUNL_SingleDoc),
    RUNTIME_CLASS(CMainFrame),    // main SDI frame window
        RUNTIME_CLASS(CUNL_SingleView));

    AddDocTemplate(pDocTemplate);



The sample application, UNL_Single, is a fairly simple application that is used to represent the doc/view framework. Notice the creation of a single document template and the use of the AddDocTemplate method. In this snippet of code, you see that the framework is adding a single template that is defined by a document class, a view class, and something I haven’t discussed yet.


Note:  

The RUNTIME_CLASS macro returns a pointer to the CRuntimeClass class. The CRuntimeClass class defines runtime attributes for the class, allowing certain information to be obtained by the owning application. Information such as object size, name, base class, and other pertinent details is stored in the CRuntimeClass class.


I have discussed the view and that the view is a way to visualize the data stored in the document, but in the Windows environment, the view must sit inside a Windows container. The CMainFrame class is that container. The menus, sidebars, and window control mechanisms are all part of the frame. As discussed earlier, the DocTemplate sets up the information that applies to the views, and it does so through the frame.

The separation of the frame components and view into two different mechanisms makes the application framework flexible. This framework puts the code functionality into three distinct areas, which allows each area to be expanded to meet the need of the application. If the view had to worry about the frame specifics, for example, each view would carry around its own framework, which would make it quite cumbersome to understand and maintain.


Note:  

Be sure to refer to the Developer Studio online help for additional information regarding other document template classes, methods, and attributes. The online help is quite good in this area.


Creating New Documents

Let’s take a closer look at what really happens when new documents are created.

I discussed the fact that the DocTemplates do a lot of work in creating and managing the doc/view architecture, but what manages the DocTemplates? Every application contains a derivative of the CWinApp class. Listing 7.3 illustrates the creation of the DocTemplate for this application, and it resides in the InitInstance method of CUNL_SingleApp, which is derived from CWinApp.

Every MFC application generated by the AppWizard will create the CWinApp class for the user. In most MFC applications, the derived CWinApp class will handle the File, Open and the File, New commands from the menu. The framework doesn’t have to worry about opening and managing files. Rather, file management is now carried out through the DocTemplates. Remember, CWinApp is derived from CCmdTarget, which allows it to process messages and events such as those presented through an applications menu. Therefore, it passes the event messages from File, Open and File, New through to the DocTemplates.

If you were to peruse AFXWIN.H, you would notice that CWinApp does not really contain the list of document templates, or does it? Look closely! It contains a pointer to the CDocManager, which is an undocumented feature that creates the binding between the CWinApp and the document templates. Check out Listing 7.4.

Listing 7.4 The CDocManager Declaration (from AFXWIN.H)


////////////////////////////////////////////////////////////////////
// CDocManager

class CDocManager : public CObject
{
    DECLARE_DYNAMIC(CDocManager)
public:

// Constructor
    CDocManager();

    //Document functions
    virtual void AddDocTemplate(CDocTemplate* pTemplate);
    virtual POSITION GetFirstDocTemplatePosition() const;
    virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const;
    virtual void RegisterShellFileTypes(BOOL bCompat);
    void UnregisterShellFileTypes();
    virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName);
    // open named file
    virtual BOOL SaveAllModified(); // save before exit
    virtual void CloseAllDocuments(BOOL bEndSession);
    // close documents before exiting
    virtual int GetOpenDocumentCount();

    // helper for standard commdlg dialogs
    virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle,
            DWORD lFlags, BOOL bOpenFileDialog,
            ÄCDocTemplate* pTemplate);

//Commands
    // Advanced: process async DDE request
    virtual BOOL OnDDECommand(LPTSTR lpszCommand);
    virtual void OnFileNew();
    virtual void OnFileOpen();


// Implementation
protected:
    CPtrList m_templateList;
    int GetDocumentCount(); // helper to count number
                            // of total documents

public:
    static CPtrList* pStaticList; // for static CDocTemplate objects
    static BOOL bStaticInit;    // TRUE during static initialization
    static CDocManager* pStaticDocManager; // for static
                                           // CDocTemplate objects

public:
    virtual -CDocManager();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

};

The line CPtrList m_templateList; contains a declaration of a CPtrList that defines the list of document templates. If you look closely, you will notice that the implementation declarations for the DocManager contain many of the same routines that the CWinApp does. These functions used to reside in CWinApp, but CWinApp now calls these functions through its pointer to the DocManager. In the MDI sample app, you’ll take a look at what the DocManager provides.

Opening New Files

The OnFileNew that CWinApp contains is actually passed through to the DocManager’s OnFileNew. OnFileNew creates a new document template. It does so through the CDocTemplate->OpenDocumentFile() routine.


Note:  

If more than one document template exists in CDocManager’s m_templateList member variable, a system dialog is presented to the user to select the appropriate template. This dialog, the CNewTypeDlg, is a simple list box that shows the available templates from which to choose. Later in this chapter, you will see how to override this feature and provide your own File, New or File, Open dialog. This will also be discussed in Chapter 21, “File I/O and MFC.”


So how do the CDocTemplate classes go about creating new documents, frames, and views? If you look closely at CDocTemplate, you see that there are two functions that do this: CreateNewDocument() and CreateNewFrame(). But what about the views? And what do these functions actually do? Listing 7.5 is from DOCTEMPL.CPP, which is part of the MFC source code. The line

CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();



does a CreateObject from the RUNTIME_CLASS CDocument. What this is doing is creating an object at runtime defined by the CDocument class. The line AddDocument(pDocument); is the AddDocument call that will add the document to the list. The CreateNewFrame is similar in what it does, by creating an object based on the CFrameWnd class defined by the application.

Listing 7.5 Document and Frame Creation (from DOCTEMPL.CPP)


CDocument* CDocTemplate::CreateNewDocument()
{
    // default implementation constructs one from CRuntimeClass
    if (m_pDocClass == NULL)
    {
        TRACE0(“Error: you must override
        ÄCDocTemplate::CreateNewDocument.\n”);
        ASSERT(FALSE);
        return NULL;
    }
    CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
    if (pDocument == NULL)
    {
        TRACE1(“Warning: Dynamic create of document type %hs failed.\n”,
            m_pDocClass->m_lpszClassName);
        return NULL;
    }
    ASSERT_KINDOF(CDocument, pDocument);
    AddDocument(pDocument);
    return pDocument;
}

////////////////////////////////////////////////////////////////////////
// Default frame creation

CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc,
ÄCFrameWnd* pOther)
{
    if (pDoc != NULL)
        ASSERT_VALID(pDoc);
    // create a frame wired to the specified document

    ASSERT(m_nIDResource != 0); // must have a resource ID
                                // to load from
    CCreateContext context;
    context.m_pCurrentFrame = pOther;
    context.m_pCurrentDoc = pDoc;
    context.m_pNewViewClass = m_pViewClass;
    context.m_pNewDocTemplate = this;

    if (m_pFrameClass == NULL)
    {
        TRACE0(“Error: you must override
        ÄCDocTemplate::CreateNewFrame.\n”);
        ASSERT(FALSE);
        return NULL;
    }
    CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
    if (pFrame == NULL)
    {
        TRACE1(“Warning: Dynamic create of frame %hs failed.\n”,
            m_pFrameClass->m_lpszClassName);
        return NULL;
    }
    ASSERT_KINDOF(CFrameWnd, pFrame);

    if (context.m_pNewViewClass == NULL)
        TRACE0(“Warning: creating frame with no default view.\n”);

    // create new from resource
    if (!pFrame->LoadFrame(m_nIDResource,
        WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,
        // default frame styles
            NULL, &context))
    {
        TRACE0(“Warning: CDocTemplate couldn’t create a frame.\n”);
        // frame will be deleted in PostNcDestroy cleanup
        return NULL;
    }

    // it worked !
    return pFrame;
}

Single Versus Multiple Document Templates

You’ve looked at the CDocManager and its relationship to the Document class and the document templates. You’ve also taken a look at some internals to get an idea of how things start fitting together. Now let’s look on the practical side of things. I’ve talked a little about document templates, but I really haven’t looked at the differences between single and multiple document template interfaces.

You know that a CSingleDocTemplate basically defines the application as having one document, which can have more than one view associated with it. Switching views in an SDI requires a little bit of work. The next section takes a look at what takes place when you switch views in an SDI.

You also know that the CMultiDocTemplate contains many document types for the application, each having one or more views associated with it. But what does that really do for you? Well, if you have multiple documents, even if the individual documents possibly represent the same data, you have the ability to have multiple views active at the same time. The SDI interface doesn’t give you that ability.

Also, the last section takes a quick look at why you might want to have an MDI appli-cation with the same document type and why you might have to control it a little differently.

Views

By now you should have an understanding of what a document is and how the CWinApp and DocTemplates interact with the CFrameWnd classes to create a framework for working with views. I haven’t talked about any specifics with views up to this point. I would like to take the next few subsections and discuss the different view types and what sets them apart. When you have that, you will see what it takes to move around the views inside an SDI application and what really happens when a new view is pulled up in an MDI application.

The CView Class

Because all MFC classes somehow derive from CObject, all view classes are derived from the CView class. The CView class provides the necessary functional elements that each view must use to function properly. Most importantly, any view class derived from CView must implement an OnDraw function to render itself.


Tip:  

The CView class is defined in AFXWIN.H. If you have the time, take a quick look at what it provides. Understanding CView will take you a long way toward understanding all the view classes.


The CScrollView Class

One CView-derived view that MFC provides is the CScrollView class. This class allows a view to scroll the data provided by the document automatically, or what appears to be automatically. If the data or object to be rendered is somewhat bigger than a normal viewport, a CScrollView should be used. This view adds scrolling regions, controllable by scrollbars on the frame, to the view area.

This view implementation solves a multitude of problems. There is, however, work that has to be done. The view must know where the scrollbars are in relation to the viewing region, and the scroll mechanism must know the size of the complete view.

SetScrollSizes() is the method used to define the size of the document to render. SetScrollSizes() requires a mapping mode, which is defined in Table 7.1. The second argument is the total size of the scroll view. The third argument is the horizontal and vertical amounts to scroll in each direction in response to a mouse click on the scrollbar. The fourth argument is the horizontal and vertical amounts to scroll in each direction in response to a mouse click on the scroll arrow. The horizontal and vertical sizes, or amounts, are defined by a SIZE structure.

Table 7.1 CScrollView Mapping Modes

Mapping Mode Logical Unit

MM_TEXT 1 pixel
MM_HIMETRIC 0.01 mm
MM_TWIPS 1/1440 in
MM_HIENGLISH 0.001 in
MM_LOMETRIC 0.1 mm
MM_LOENGLISH 0.01 in



The CScrollView responds automatically to not only the scrollbar actions, but also those of the keyboard, such as word wrap, paging, and other functions that would cause the focus to extend past the current viewing port.

To scale the viewport to the current window size, use the SetScaleToFitSize() method. This will appear to pop off the scrollbars, but what actually is happening is that the entire document is being scaled to fit inside the present window. This zoom capability allows documents that are only a fraction bigger than a view window to be viewed inside the window without the hindrances of scrollbars.

The CFormView Class

I’m sure that you are probably aware of some form-based application or dialog that contained data entry boxes to enter data into a database or spreadsheet. MFC provides a view, derived from CView, that provides the basic functional elements to enable the developer to create a view based on dialog resources. Essentially, the developer will create a dialog similarly to creating one for a dialog-based application. The DDX mechanisms are then tied to the CFormView class, and the framework takes care of rendering the dialog within the view’s framework.

The Database View Classes

I’ve covered the CFormView just briefly and indicated that it can be used to create a data entry application for a database. There are views that encapsulate the database-document framework for ODBC and DAO.

A CDaoRecordView object is a view that displays database records in controls similar to the CFormView. In fact, the view is a form view! It is directly connected to a CDaoRecordset object. The CDaoRecordView automates the implementation for moving to the first, next, previous, or last record and provides an interface for updating the record currently in view. Wow! This makes short work of having to create form views and manually code the data exchange mechanisms for moving around the database.

The CRecordView is essentially the same mechanism, but uses the ODBC layer instead of the DAO layer.


Note:  

The DAO layer is the MFC classes that encapsulate the Jet database engine. The other database classes are referred through the ODBC layer. Refer to Chapter 18, “MFC Database Processing,” for a discussion on DAO and ODBC.


The Control Views

There is a group of views that are essentially control containers. The views, although derived from CView, are nothing more than a single control contained within the frame of a single view. These are as follows:

  CTreeView—This view encapsulates the CTreeCtrl class inside the doc/view architecture.
  CEditView—This view encapsulates the CEditCtrl class inside the doc/view architecture.
  CRichEditView—This view encapsulates the CRichEditCtrl class inside the doc/view architecture.
  CListView—This view encapsulates the CListCtrl class inside the doc/view architecture.

All these classes are derived from CCtrlView, which is derived from CView. Each of these view types has a member function that will return the appropriate control for manipulation. This method of exposing the control makes life easy for developing useful applications.

Let’s expand the CEditView because UNL_SingleApp contains an edit view. The CEditView breaks the framework a little here. The data that is contained within the CEditCtrl is actually contained in the control and not the document. Is that okay? Although the document is normally the place to hold data, this exception is acceptable. The control contained within the view is entirely self-contained. This makes it easy to work with as well. There is a function in CEditView called GetEditCtrl() that will return the edit control. Functions such as saving data and copying data are performed on the edit control directly.

Listing 7.6 is the header file for the UNL_Single application. Listing 7.7 is the implementation of that class. Notice that this doesn’t appear much different than other view definitions. The encapsulation of the control within the view is implemented at a lower level (CCtrlView).


Caution:  

You have to be careful when working with a control view. Because the control is contained by a view, that view must be aware of the state of the control at all times! Changing certain characteristics might create unwanted results.


Listing 7.6 The UNL_EdView Class Definition


#if !defined(AFX_UNL_EDVIEW_H__5C9EF3D1_D28B_11D2_9116
Ä_00C04FBEDB74__INCLUDED_)
#define AFX_UNL_EDVIEW_H__5C9EF3D1_D28B_11D2_9116_00C04FBEDB74__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// UNL_EdView.h : header file
//

/////////////////////////////////////////////////////////////////////////
// CUNL_EdView view

class CUNL_EdView : public CEditView
{
// protected: // create from serialization only
public: // change from protected to public for view switching

    CUNL_EdView(); // protected constructor used by dynamic creation
    DECLARE_DYNCREATE(CUNL_EdView)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CUNL_EdView)
    protected:
    virtual void OnDraw(CDC* pDC); // overridden to draw this view
    //}}AFX_VIRTUAL

// Implementation
protected:
    virtual -CUNL_EdView();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

    // Generated message map functions
protected:
    //{{AFX_MSG(CUNL_EdView)
        // NOTE - the ClassWizard will add and remove member
        // functions here.
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
// immediately before the previous line.

#endif // !defined(AFX_UNL_EDVIEW_H__5C9EF3D1_D28B_11D2_9116
Ä00C04FBEDB74__INCLUDED_)

Listing 7.7 The UNL_EdView Class Implementation


// UNL_EdView.cpp : implementation file
//

#include “stdafx.h”

#include “UNL_SingleDoc.h”

#include “UNL_Single.h”
#include “UNL_EdView.h”

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////
// CUNL_EdView

IMPLEMENT_DYNCREATE(CUNL_EdView, CEditView)

CUNL_EdView::CUNL_EdView()
{
}

CUNL_EdView::-CUNL_EdView()
{
}


BEGIN_MESSAGE_MAP(CUNL_EdView, CEditView)
    //{{AFX_MSG_MAP(CUNL_EdView)
        // NOTE - the ClassWizard will add and remove
        // mapping macros here.
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////
// CUNL_EdView drawing

void CUNL_EdView::OnDraw(CDC* pDC)
{
}

//////////////////////////////////////////////////////////////
// CUNL_EdView diagnostics

#ifdef _DEBUG
void CUNL_EdView::AssertValid() const
{
    CEditView::AssertValid();
}

void CUNL_EdView::Dump(CDumpContext& dc) const
{
    CEditView::Dump(dc);
}
#endif //_DEBUG

//////////////////////////////////////////////////////////////

// CUNL_EdView message handlers



Changing Views in an SDI

If you are familiar with the MDI interface, you are probably painfully aware of the complications that you can get into managing multiple documents. The framework goes a long way in solving this dilemma, but let’s face it—the MDI framework is not the prettiest to look at or the easiest to use (from a user’s perspective). Most users prefer a single view to work in, and if they want to change views, they can do so through a menu or accelerator keys.

Suppose that you have a text editor or a view that represents ASCII data. In addition, though, imagine you also need another view that allows the user to enter information via a form. The sample program represented by the MDI has a CFormView-based view that enables users to enter strings in a list. The Format button will take the list of strings and write them to a comma-delimited file. Although this is a functional element of many database utility programs, it is somewhat overrepresented by the MDI. There are other situations in which you might want to consider implementing an SDI.

Let’s take a close look at what you have to do to enable view switching within your SDI. The following numbered subsections outline the functional steps that need to be performed to do the view switching:

  Adding a second (or other) view class—The first step is obviously to define another view to switch to. Remember that this view is essentially owned by a single document.
  Modifying the frame class—Now you need to insert the switching code to either the frame class or the view class. Remember that you have only one frame class and more than one view. For the sake of simplifying the code, you will do this from the frame class.
  Implementing the switching code—Now that you’ve modified the header to add the new function, it’s time to write the code to switch the views.
  Telling the document about its new view—After you have switched your views, you need to set the appropriate pointers into the document class.
  Defining the resources to allow the switching messages—Without a way to exercise your switching code, the application is not useful to the user.

Step 1: Adding Another View

The first step in expanding the restraints of an SDI application is to add a desired view. This view can be a control-based view, a form view, or a simple view for drawing purposes. You can let your mind go wild with the expansion possibilities. To add this view, you can use the ClassWizard to create the view. If it’s a form view that you are adding, you would use the resource editor to define the view’s dialog.

Step 2: Modifying the Frame Class

Because you know that the frame class does most of the work in drawing views and managing view characteristics, it makes perfect sense to manage the view switching inside the frame class. Take a look at Listing 7.8.

Listing 7.8 The MainFrm Class Definition


// MainFrm.h : interface of the CMainFrame class
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__9442E449_C853_11D2_9113
Ä_00C04FBEDB74__INCLUDED_)
#define AFX_MAINFRM_H__9442E449_C853_11D2_9113_00C04FBEDB74__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd
{

protected: // create from serialization only
    CMainFrame();
    DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CMainFrame)
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual CMainFrame();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected: // control bar embedded members
    CStatusBar m_wndStatusBar;
    CToolBar   m_wndToolBar;

// Generated message map functions
protected:
    //{{AFX_MSG(CMainFrame)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void SwitchViews();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()

public:

    int   m_nCurrentView;
};

//////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
// immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__9442E449_C853_11D2_9113

       // _00C04FBEDB74__INCLUDED_)

Notice the declaration of the SwitchViews method and the m_nCurrentView member variable. (More about these later.) This is basically all you have to do to modify the Main Frame declaration. These are discussed in the next section.

Step 3: Implementing the View-Switching Code

Now that you’ve defined the functions that need to be implemented, take a look at the implementation shown in Listing 7.9.

Listing 7.9 The SwitchViews Method


BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
    ON_WM_CREATE()
    ON_COMMAND(ID_VIEW_SWITCHVIEWS, SwitchViews)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

01:  //////////////////////////////////////////////////////////////
02:  // CMainFrame message handlers
03:
04:  void CMainFrame::SwitchViews()
05:  {
06:      CDocument*  pDoc = GetActiveDocument();
07:
08:      CView* pCurrView = GetActiveView(); // save old view
09:      CView* pNewView;
10:
11:      if (m_nCurrentView == 0)
12:        {
13:              pNewView = (CView*)new CUNL_SingleView;
14:              m_nCurrentView = 1;
15:        }
16:        else
17:        {
18:             pNewView = (CView*)new CUNL_EdView;
19:             m_nCurrentView = 0;
20:        }
21:
22:
23:        pNewView->Create(  NULL,
24:                              NULL,
25:                              AFX_WS_DEFAULT_VIEW,
26:                              rectDefault,
27:                              this,
28:                              AFX_IDW_PANE_FIRST,
29:                              NULL  );
30:
31:
32:
33:        pNewView->OnInitialUpdate();
34:
35:        pNewView->ShowWindow(SW_SHOW); // show it..
36:        pCurrView->ShowWindow(SW_HIDE); // don’t forget to hide
           Äour current (old) view
37:
38:        pDoc->AddView(pNewView);
39:        pDoc->RemoveView(pCurrView);
40:
41:        SetActiveView(pNewView); // let’s go with it..
42:
43:     RecalcLayout();
44:
45:  }



Notice line 6. To enable step 4 to be successful, you have to have a pointer to an owning document. To switch views, you have to maintain a pointer to your current view and also one to the new one you are switching to. The m_nCurrentView member variable keeps track of which view you are using. There are other methods for maintaining this count, but you are interested in the view-switching steps themselves. Depending on which view you have active, you have to create the other view by using the Create method. After the Create is performed, you do an OnInitialUpdate on the view to perform any initialization that might be specific to the view’s first rendering. You then have to show your new view and hide your old view.

Step 4: Updating the Document

Lines 38–41 perform the update to the necessary pointers to the document. You first Add the new view to the document and then Remove the old view. You then activate the new view. See how simple this is!

Step 5: Defining the Resources You Need

When you are done with the code, you are almost there. You now have to figure a way to activate the switching code. This is most often done through the menu resource, as in the UNL_Single application. Notice the Switch Views menu command on the View menu. Notice the message map declared in Listing 7.9 before the line-numbered section. This map defines the DDX/DDV command map. Notice the ON_COMMAND for the SwitchViews method. Whenever the menu command is selected, the SwitchViews method is activated. Congratulationsyou now have a switching-view SDI application!

Using the MDI

I’ve been discussing primarily the SDI, but a truly robust application would have to manipulate many forms of data to be productive. One way to do this would be to create an MDI application. With the advancing popularity of COM, many functions that were performed by single MDI applications are now being handled through a variety of functions. Indeed, you might have already noticed the decreasing popularity of some midsize applications. However, if you are saddled with creating a large desktop application, you will need to understand the MDI.

With an MDI, the framework essentially creates a list of document templates. Every time the AddDocTemplate function is called, a template is added to a linked list of templates. (You should be seeing the flexibility here!) With a linked list of templates, each having the possibility of having multiple views, you can quickly see that data can be represented in a multitude of ways.

An SDI application creates a CFrameWnd instance, usually in MainFrm.cpp. An MDI application, on the other hand, creates a CMDIFrameWnd instance. As part of this construct, the CMDIFrameWnd contains a child window frame for each document template. This is defined by deriving from CMDIChildWnd. This allows the application to have multiple viewing frames contained within the main frame window.

Take a look at the UNL_MultiEd sample application. Notice the frame windows that are associated with each document template type. To handle command messages, these frame windows contain overridden message handlers that are applied for each menu or toolbar command.


Note:  

I normally make it a point to create a menu for each document type (child frame) and then override all possible commands. This saves confusion in the long run and makes the application easier to maintain. This, however, is just one way to handle messages for the application. You can choose to have the main frame window process the messages and maintain a single menu for all child frames. If you do this, don’t forget that you need to keep track of which document/view component is active.


Finding a Document Template

Some applications require that you override the framework’s automatic creation of a view at startup. The framework creates an InitInstance method of the derived CWinApp class that will automatically display the child frame window that was created as part of the initial framework. This arrangement is depicted in Listing 7.10.

Listing 7.10 The Standard CWinApp::InitInstance


// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
    return FALSE;

// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();

return TRUE;



However, if you define multiple documents, and you want to control how these are created (or opened), Listing 7.11 is a portion of what you need to do.

Listing 7.11 Removing the Automatic Frame Startup


// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

if (CCommandLineInfo::FileNew == cmdInfo.m_nShellCommand)
    cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;

if (!ProcessShellCommand(cmdInfo)) return FALSE;

// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
    return FALSE;

// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();

return TRUE;

Essentially, you are telling the framework, through CCommandLineInfo::FileNothing, that you will be handling the frame opening through FileNew and FileOpen methods in the main frame.

When you have this portion of code in, you need to create a FileNew command message handler to open the document you want. MFC provides a default FileNew dialog that will display the registered templates and provide a selection list to the user. The user can then select the document type with which to open. This is fine if you think your user will be able to understand what document types are and how they are applied to what he or she is doing. In most cases, the application developer wants to develop the application in such a manner as to control what the user sees and how he or she works. The MDI framework is so flexible that a myriad of options is available to the developer. In some cases, the developer might want only one document type to be opened, and that document type is essentially the controlling document for all other documents. In some cases, the developer might want to create a unique OnFileNew dialog that displays the document type in a more “readable” fashion. Either way, it is imperative that the developer understand how to find a document template.

Listing 7.12 is the OnFileNew handler for the UNL_MultiEd sample application. Notice the line POSITION curTemplatePos = pApp->GetFirstDocTemplatePosition();. This is essentially positioning to the front of the linked list of document templates. The application object maintains this list. From that point, you iterate the list to find the document template you need. Notice the line if(str == _T(“StringForm”)). The document template maintains a “describing” string you can use to search for templates that meet your criteria.

Listing 7.12 The OnFileNew Handler (Typical)


void CMainFrame::OnFileNew()
{
    //
    // First get our app, and then position to the first
    // template. Iterate your templates, until you find
    // the desired one.
    //
       CUNL_MultiEdApp* pApp = (CUNL_MultiEdApp*)AfxGetApp();

    POSITION curTemplatePos = pApp->GetFirstDocTemplatePosition();

    while(curTemplatePos != NULL)
    {
        CDocTemplate* curTemplate =
            pApp->GetNextDocTemplate(curTemplatePos);
        CString str;
        curTemplate->GetDocString(str, CDocTemplate::docName);
        if(str == _T(“StringForm”))
        {
                CStringDoc* pCSDoc;
            pCSDoc = (CStringDoc*)curTemplate->OpenDocumentFile(NULL);
            return;
        }
    }
    AfxMessageBox(“Why are we here ??”);
}

Managing Data in MDI Applications

Although most developers start out by managing the applications data inside the document, this shouldn’t be a limiting factor. There is no constraint inside MFC that dictates that the developer must represent all application data inside a CDocument class. In fact, in the case of the UNL_MultiEd application, I broke with tradition and created a global StringKeeper class mechanism to manage the application’s data. If you maintain a pointer to the class instance inside the application object, the data is only an AfxGetApp call away.

In some cases, this example might need to be expanded, depending on the application’s requirements. For example, on one project that I worked on a few years back, the application represented a design process in which one phase relied on information from a previous phase. The phases were uniquely different from one another, thus requiring an MDI architecture. To manage the data, a global keeper class was created to manage the context of the data that was shared between phases. Because the data was maintained in a database, this keeper class managed the transition and context between phases. The point here is that a developer should not be limited to the framework. Make it work for you!

Summary

You’ve just taken a whirlwind tour of a fairly robust architecture. You know that not all applications are created equal. Some applications maintain a fairly simple user interface and can be represented by a single document. Other applications require a more flexible approach in being able to represent many forms of different data in the same application. MFC, by providing the document/view architecture, gives the developer a starting point for creating truly robust applications. The application developer can choose to stay within the framework and create solid applications with a minimal level of difficulty. He or she might also choose to create a truly flexible application by expanding the framework. At this point, the developer is armed with enough information to actively consider many possible choices when designing user interface applications.